﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace GeoParser
{
	/// <summary>
	/// Represents a distance and unit.
	/// </summary>
	public class Distance : IComparable<Distance>, IEquatable<Distance>, IFormattable, ICloneable
	{
		public enum Unit
		{
			Miles,
			Km,
			NauticalMiles
		}

		private readonly double m_Miles;

		public Distance()
		{
		}

		public Distance(double value, Unit unit)
		{
			switch (unit)
			{
				case Unit.Miles:
					m_Miles = value;
					break;
				case Unit.Km:
					m_Miles = value / KmPerMile;
					break;
				case Unit.NauticalMiles:
					m_Miles = value / NautPerMile;
					break;
			}
		}

		public static readonly double KmPerMile = 1.609344;
		public static readonly double NautPerMile = 0.868423;

		public double Miles
		{ 
			get { return m_Miles; }
		}

		public double Km
		{
			get { return m_Miles * KmPerMile; }
		}

		public double NauticalMiles
		{
			get { return m_Miles * NautPerMile; }
		}

		public override int GetHashCode()
		{
			return m_Miles.GetHashCode();
		}

		public static Distance Length(IEnumerable<GeoPoint> points)
		{
			if (points == null)
				return new Distance();

			var list = points.ToList();
			Distance len = new Distance();
			for (int i = 0; i < list.Count - 1; i++)
			{
				len += Waypoint.DistanceBetween(list[i], list[i + 1]);
			}

			return len;
		}

		public static Distance operator +(Distance a, Distance b)
		{
			return new Distance(a.Miles + b.Miles, Unit.Miles);
		}

		#region IComparable<Distance> Members

		public int CompareTo(Distance other)
		{
			return this.Miles.CompareTo(other.Miles);
		}

		#endregion

		public override bool Equals(object obj)
		{
			if (obj == null)
				return false;

			if (object.ReferenceEquals(this, obj))
				return true;

			if (this.GetType() != obj.GetType())
				return false;

			return this.Equals(obj as Distance);
		}

		#region IEquatable<Distance> Members

		public bool Equals(Distance other)
		{
			return other != null && other.Miles.Equals(Miles);
		}

		#endregion

		/// <summary>
		/// Convert distance to string in the form "x miles".
		/// </summary>
		/// <returns>String form.</returns>
		public override string ToString()
		{
			return string.Format("{0:F1} miles", Miles);
		}

		public string ToString(string format)
		{
			return ToString(format, CultureInfo.CurrentCulture);
			throw new NotImplementedException();
		}

		#region IFormattable Members

		/// <summary>
		/// Convert distance to string in specified form. Format ff:d where ff can be one of "km", "nm" or "m" and d is the
		/// number of decimal places (default 1).
		/// </summary>
		/// <param name="format">Specified format.</param>
		/// <returns>Distance formatted string.</returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			if (format == null)
				throw new ArgumentNullException("format");

			// Parse decimal places after colon.
			string[] parts = format.Split(':');
			int deci;
			if (parts.Length > 1)
				deci = int.Parse(parts[1]);
			else
				deci = 1;

			// Return the different values according to the format string.
			if (format.StartsWith("km"))
				return string.Format(formatProvider, "{0:F" + deci + "} km", Km);
			else if (format.StartsWith("nm"))
				return string.Format(formatProvider, "{0:F" + deci + "} nm", NauticalMiles);
			else if (format.StartsWith("m"))
				return string.Format(formatProvider, "{0:F" + deci + "} m", Km * 1000);
			else
				return string.Format(formatProvider, "{0:F" + deci + "} miles", Miles);
		}

		#endregion

		public static Boolean operator ==(Distance a, Distance b)
		{
			if (object.ReferenceEquals(a, null))
				if (object.ReferenceEquals(b, null))
					return true;
				else
					return false;

			return a.Equals(b);
		}

		public static Boolean operator !=(Distance a, Distance b)
		{
			return !(a == b);
		}

		#region ICloneable Members

		public object Clone()
		{
			return new Distance(Miles, Unit.Miles);
		}

		#endregion
	}
}